package rad.framework.ejb3;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.PostActivate;
import javax.ejb.PrePassivate;
import javax.interceptor.InvocationContext;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import rad.framework.ejb3.EJBUtil;

import rad.framework.jmx.JMXUtil;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.annotation.ejb.Depends;
import org.jboss.seam.Component;
import org.jboss.seam.annotations.In;

/**
 * Intercepts stateful session bean to inject all the dependencies when the bean
 * is activated. Currently JBoss EJB 3.0 container doesn't do the injection,
 * which is legitimate according to EJB 3 spec. Read more on this here:
 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6419979
 * 
 * @author Chunyun Zhao
 */
public class PostActivateInterceptor {
	private static final Log log = LogFactory.getLog(PostActivateInterceptor.class);
	
	@PrePassivate
	public void prePassivate(InvocationContext ctx) {
		log.debug("In PostActivateInterceptor prePassivate");
	}

	@PostActivate
	public void postActivate(InvocationContext ctx) throws Exception {
		log.debug("In PostActivateInterceptor postActivate");
		Object target = ctx.getTarget();
		
		Field[] fields = getAllInjectedTransientFields(target.getClass());
		for (Field field : fields ) {
			field.setAccessible(true);
			log.debug("Field : " + field.getName() + ", value: " + field.get(target));
			Annotation[] annotations = field.getAnnotations();
			for ( Annotation annotation : annotations ) {
				try {
					Object value = null;
					if ( annotation instanceof EJB ) {
						value = locateEjb(field, (EJB)annotation);
					} else if ( annotation instanceof Depends ) {
						value = locateMbean(field, (Depends)annotation);
					} else if ( annotation instanceof Resource ) {
						value = locateResource(field, (Resource)annotation);
					} else if ( annotation instanceof In ) {
						value = locateSeamComponent(field, (In)annotation);
					}
					
					if ( value != null ) {
						field.set(target, value);
						log.debug("Injected " + value + " to field " + field.getName() + " of target " + target.getClass());
					}
				} catch (Throwable t) {
					log.error("Unable to perform injection with field " + field.getName() + " on " + target, t);
				}
			}
		}
		ctx.proceed();
	}

	private Object locateSeamComponent(Field field, In in) {
		String componentName = in.value();
		if ( componentName == null ) {
			componentName = field.getName();
		}
		log.debug("Trying to locate seam compoent for field " + field.getName() + " using component name " + componentName);
		return Component.getInstance(componentName);
	}

	private Object locateResource(Field field, Resource resource) throws NamingException, IllegalArgumentException, IllegalAccessException {
		String mappedName = resource.mappedName();
		Object result = null;
		log.debug("Trying to locate resource for field " + field.getName() + " using mappedName " + mappedName);
		if ( StringUtils.isNotEmpty(mappedName) ) {
			result = new InitialContext().lookup(mappedName);
			log.debug("Found resource " + result);
		}
		return result;
	}

	private Object locateMbean(Field field, Depends depends) throws MalformedObjectNameException, NullPointerException, IllegalArgumentException, IllegalAccessException {
		Object result = null;
		String[] values = depends.value();
		Class fieldType = field.getType();
		log.debug("Trying to locate MBean for field " + field.getName() + " using value " + Arrays.toString(values));
		if ( values.length > 0 && StringUtils.isNotEmpty(values[0])) {
			ObjectName objectName = new ObjectName(values[0]);
			result = JMXUtil.getMBean(fieldType, objectName);
			log.debug("Found MBean " + result);
		}
		return result;
	}

	private Object locateEjb(Field field, EJB ejb) throws IllegalArgumentException, IllegalAccessException, NamingException {
		Class fieldType = field.getType();
		log.debug("Trying to locate EJB for field " + field.getName() + " using fieldClass " + fieldType.getName());
		Object result = EJBUtil.locateEjb(fieldType);
		log.debug("Found EJB " + result);
		return result;
	}

	private Field[] getAllInjectedTransientFields(Class<? extends Object> clazz) {
		List<Field> fields = new ArrayList<Field>();
		while ( !clazz.equals(Object.class) ) {
			Field[] declaredFields = clazz.getDeclaredFields();
			for (Field field : declaredFields) {
				if ( field.isAnnotationPresent(EJB.class) 
						|| field.isAnnotationPresent(Resource.class) 
						|| field.isAnnotationPresent(Depends.class)
						|| field.isAnnotationPresent(In.class)) {
					if ( Modifier.isTransient(field.getModifiers())){ 
						fields.add(field);
					}
				}
			}
			clazz = clazz.getSuperclass();
		}
		return fields.toArray(new Field[fields.size()]);
	}


}
